package io.hellsinger.filesystem.linux.operation

import io.hellsinger.filesystem.attribute.PathType
import io.hellsinger.filesystem.checkScheme
import io.hellsinger.filesystem.directory.DirectoryEntry
import io.hellsinger.filesystem.error.PathDoesNotExist
import io.hellsinger.filesystem.linux.LinuxFileOperationProvider
import io.hellsinger.filesystem.linux.LinuxOperationOptions
import io.hellsinger.filesystem.linux.directory.LinuxDirectory
import io.hellsinger.filesystem.linux.error.ErrnoException
import io.hellsinger.filesystem.linux.file.LinuxFile
import io.hellsinger.filesystem.path.Path
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

// Optimized way
private fun <P : Path> P.treeDeletion(): Flow<DirectoryEntry<P>> =
    flow {
        val pointer = LinuxFileOperationProvider.openDirectoryPointer(bytes)
        var entry = LinuxFileOperationProvider.getDirectoryEntry(pointer)
        while (entry != null) {
            val attrs =
                LinuxFileOperationProvider.getStatusAtDir(pointer, entry.path) ?: PathDoesNotExist(
                    resolve(entry.path),
                )
            if (attrs.type == PathType.Directory) {
                emitAll((resolve(entry.path) as P).treeDeletion())
                LinuxFileOperationProvider.unlinkAtDir(
                    pointer,
                    entry.path,
                    LinuxOperationOptions.Delete.REMOVE_DIRECTORY,
                )
            } else {
                LinuxFileOperationProvider.unlinkAtDir(
                    pointer,
                    entry.path,
                    LinuxOperationOptions.Delete.REMOVE_DIRECTORY,
                )
            }

            entry = LinuxFileOperationProvider.getDirectoryEntry(pointer)
        }

        LinuxFileOperationProvider.closeDirectoryPointer(pointer)
    }

@Throws(ErrnoException::class)
suspend fun <P : Path> LinuxDirectory<P>.delete(onDeleteTreeError: suspend (Throwable) -> Unit = {}) {
    checkScheme(LinuxOperationOptions.SCHEME)
    path.treeDeletion().catch { error -> onDeleteTreeError(error) }.collect()
    LinuxFileOperationProvider.removeDirectory(path.bytes)
}

suspend fun <P : Path> LinuxFile<P>.delete() =
    suspendCoroutine { continuation ->
        checkScheme(LinuxOperationOptions.SCHEME)
        try {
            continuation.resume(LinuxFileOperationProvider.unlink(path.bytes))
        } catch (errno: ErrnoException) {
            continuation.resumeWithException(errno)
        }
    }
